package jp.wug.core;

import com.google.inject.TypeLiteral;
import jp.wug.Keys;
import jp.wug.Renderer;
import jp.wug.core.ControllerFactory.ControllerBuilder;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.inject.Provider;
import java.io.IOException;
import java.io.Writer;
import java.util.*;

public class TemplateEngine {
    public static final String NS = "http://wug.jp/2009/wug";

    private final Handler handler;

    public TemplateEngine() {
        Handler[] handlers = {new FirstHandler(), new IfHandler(), new IfNotHandler(), new ChildHandler(), new CatchHandler(), new EachHandler(), new MapHandler(), new RenderHandler(), new Html.OptionHandler(), new AttrHandler(), new ResourceAttrHandler(), new Html.LinkHandler(), new Html.ActionHandler(), new Html.FormHandler(), new Html.SelectHandler(), new Html.InputHandler(), new Html.RadioHandler(), new Html.SubmitHandler(), new Html.ForHandler(), new LabelHandler(), new ResourceHandler(), new ExportHandler(), new ImportHandler(), new RawHandler(), new LastHandler(this)};
        for (int i = 0; i < handlers.length - 1; i++) {
            handlers[i].setNext(handlers[i + 1]);
        }
        handler = handlers[0];
    }

    Writable process(NodeList nodeList, ControllerFactory.ControllerBuilder builder) {
        List<Writable> list = new ArrayList<Writable>();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node child = nodeList.item(i);
            list.add(process(child, builder));
        }
        if (list.size() == 0) {
            return TemplateEngine.EMPTY_TEMPLATE;
        } else {
            return new TemplateList(list);
        }
    }

    private Writable process(Node node, ControllerFactory.ControllerBuilder builder) {
        switch (node.getNodeType()) {
            case Node.ELEMENT_NODE:
                Element element = (Element) node;
                try {
                    return handler.construct(element, Output.create(element, true), builder);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException(node + ": ", e);
                }
            case Node.TEXT_NODE:
                return new StringTemplate(TemplateEngine.escapeText(node.getNodeValue()));
            case Node.CDATA_SECTION_NODE:
                return createCData(node.getNodeValue());
            case Node.PROCESSING_INSTRUCTION_NODE:
                return createProcessingInstruction(node.getNodeName(), node.getNodeValue());
            case Node.COMMENT_NODE:
                return createComment(node.getNodeValue());
            default:
                throw new IllegalArgumentException("unsupported node type:" + node.getNodeType());
        }
    }

    Writable createCData(String cdata) {
        return new StringTemplate("<![" + cdata + "]]>");
    }

    Writable createProcessingInstruction(String name, String value) {
        if (value == null || value.length() == 0) {
            return new StringTemplate("<? " + name + "?>");
        } else {
            return new StringTemplate("<? " + name + " " + value + "?>");
        }
    }

    Writable createComment(String value) {
        return new StringTemplate("<!--" + value + "-->");
    }

    static String escapeText(String string) {
        return string.replace("&", "&amp;").replace("<", "&lt;").replace("<", "&gt;");
    }

    static String escapeAttribute(String string) {
        return string.replace("&", "&amp;").replace("<", "&lt;").replace("<", "&gt;").replace("\"", "&quot;");
    }

    private static class StringTemplate implements Writable {
        private final String string;

        private StringTemplate(String string) {
            this.string = string;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            writer.write(string);
        }
    }


    static final Writable EMPTY_TEMPLATE = new Writable() {
        public void write(Writer writer, WritableContext context) {
        }
    };

    private static class TemplateList implements Writable {
        private final List<Writable> templates;

        TemplateList(List<Writable> templates) {
            this.templates = Collections.unmodifiableList(templates);
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            for (Writable template : templates) {
                template.write(writer, context);
            }
        }
    }

    private abstract static class Handler {
        private Handler next;
        private String attribute;

        Handler() {
            this(null);
        }

        Handler(String attribute) {
            this.attribute = attribute;
        }

        void setNext(Handler next) {
            this.next = next;
        }

        abstract Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException;

        private Handler chain(Element element) {
            if (attribute != null && !element.hasAttributeNS(TemplateEngine.NS, attribute)) {
                return next.chain(element);
            } else {
                return this;
            }
        }

        Writable chain(Element element, Output output, ControllerFactory.ControllerBuilder builder) {
            Handler handler = next.chain(element);
            try {
                return handler.construct(element, output, builder);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(builder + " - " + handler.attribute + " in " + element + ": ", e);
            }
        }
    }

    private static class Output {
        private final SortedMap<String, Writable> attributes = new TreeMap<String, Writable>();
        private Writable content;
        private final List<Writable> insertedContent = new ArrayList<Writable>();

        static Output create(Element element, boolean removeWug) {
            Output output = new Output();
            NamedNodeMap attributes = element.getAttributes();
            for (int i = 0; i < attributes.getLength(); i++) {
                Node node = attributes.item(i);
                if (removeWug) {
                    if (TemplateEngine.NS.equals(node.getNamespaceURI())) {
                        /*skip 'wug:foo="bar"*/
                        continue;
                    } else if ("http://www.w3.org/2000/xmlns/".equals(node.getNamespaceURI()) && TemplateEngine.NS.equals(node.getNodeValue())) {
                        /*skip 'xmlns:wug="http://..."'*/
                        continue;
                    }
                }
                String name;
                String prefix = node.getPrefix();
                String localName = node.getLocalName();
                if (prefix == null) {
                    name = localName;
                } else {
                    name = prefix + ":" + localName;
                }

                Writable attribute = new ConstantAttributeTemplate(name, node.getNodeValue());
                output.putAttribute(name, attribute);
            }
            return output;
        }

        void putAttribute(String name, Writable template) {
            attributes.put(name, template);
        }

        Writable[] getAttributes() {
            Collection<Writable> writableCollection = attributes.values();
            return writableCollection.toArray(new Writable[writableCollection.size()]);
        }

        void setContent(Writable content) {
            this.content = content;
        }

        void insert(Writable content) {
            insertedContent.add(content);
        }

        Writable construct(TemplateEngine templateEngine, Element element, ControllerFactory.ControllerBuilder builder) {
            List<Writable> writables = new ArrayList<Writable>();
            writables.addAll(insertedContent);

            Writable child;
            if (content != null) {
                child = content;
            } else {
                child = templateEngine.process(element.getChildNodes(), builder);
            }
            if (child != TemplateEngine.EMPTY_TEMPLATE) {
                writables.add(child);
            }

            if (element.getNamespaceURI().equals(TemplateEngine.NS) && "e".equals(element.getLocalName())) {
                return new TemplateList(writables);
            }

            return new ElementTemplate(element.getPrefix(), element.getLocalName(), getAttributes(), writables.toArray(new Writable[writables.size()]));
        }
    }

    private static class FirstHandler extends Handler {
        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            return chain(element, output, builder);
        }
    }

    private static class IfHandler extends Handler {
        IfHandler() {
            super("if");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "if");
            Expression<Void, Boolean> expression = builder.call(value).asBoolean();
            Writable child = chain(element, output, builder);
            return new IfTemplate(child, expression, true);
        }
    }

    private static class IfNotHandler extends Handler {
        IfNotHandler() {
            super("if-not");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "if-not");
            Expression<Void, Boolean> expression = builder.call(value).asBoolean();
            Writable child = chain(element, output, builder);
            return new IfTemplate(child, expression, false);
        }
    }

    private static class IfTemplate implements Writable {
        private final Writable child;
        private final Expression<Void, Boolean> expression;
        private final boolean value;

        IfTemplate(Writable child, Expression<Void, Boolean> expression, boolean value) {
            this.child = child;
            this.expression = expression;
            this.value = value;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            if (context.getBoolean(expression) == value) {
                child.write(writer, context);
            }
        }
    }

    private static class ChildHandler extends Handler {
        ChildHandler() {
            super("child");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "child");
            Expression<Void, Model> expression = builder.call(value).asChild();
            Writable child = chain(element, output, builder.getChild(expression));
            return new ChildTemplate(expression, child);
        }
    }

    private static class ChildTemplate implements Writable {
        private final Expression<Void, Model> expression;
        private final Writable child;

        ChildTemplate(Expression<Void, Model> expression, Writable child) {
            this.expression = expression;
            this.child = child;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            WritableContext newContext = context.putModel(expression);
            child.write(writer, newContext);
        }
    }

    private static class CatchHandler extends Handler {
        private final Map<String, Class<?>> exceptionTypes = new HashMap<String, Class<?>>();

        CatchHandler() {
            super("catch");
            for (Class<?> c : new Class[]{NumberFormatException.class, IllegalArgumentException.class}) {
                exceptionTypes.put(c.getSimpleName(), c);
            }
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "catch");
            String[] args = value.split(",");
            TypeLiteral<?> exceptionType;
            try {
                exceptionType = TypeLiteral.get(getExceptionType(args[0]));
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }

            String key = builder.registerCatch(args[1], exceptionType);
            ControllerFactory.ControllerBuilder childBuilder = builder.getCatch(key);
            Writable child = chain(element, output, childBuilder);
            return new CatchWritable(child, exceptionType, key, childBuilder.getModelFactory());
        }

        private Class<?> getExceptionType(String name) throws ClassNotFoundException {
            Class<?> c = exceptionTypes.get(name);
            if (c != null) {
                return c;
            }
            return Class.forName(name);
        }
    }

    private static class CatchWritable implements Writable {
        private final Writable content;
        private final String key;
        private final TypeLiteral<?> type;
        private final ModelFactory modelFactory;

        CatchWritable(Writable content, TypeLiteral<?> type, String key, ModelFactory modelFactory) {
            this.content = content;
            this.key = key;
            this.type = type;
            this.modelFactory = modelFactory;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            for (ModelException modelException : context.getExceptions(type, key)) {
                Model model = modelFactory.create(modelException.getCause());
                WritableContext childContext = context.putCatch(model);
                content.write(writer, childContext);
            }
        }
    }

    /*TODO: rename to "unique", "keys", "keyed" or "absolute"?*/
    private static class EachHandler extends Handler {
        EachHandler() {
            super("each");
        }

        @Override
        Writable construct(Element element, Output output, ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "each");

            Expression<Void, Model> keysExpression = builder.call(value).asChild();
            if (!keysExpression.getModelFactory().getType().getRawType().equals(Keys.class)) {
                throw new RuntimeException(builder.getType() + "." + keysExpression + " must return Keys");
            }

            Expression<String, Model> getterExpression = builder.createEach(keysExpression);
            String key = builder.registerEach(getterExpression);
            ControllerBuilder childController = builder.registerUniqueChild(getterExpression);

            Writable content = chain(element, output, childController);

            Expression<Void, Iterable<String>> expression = builder.call(value).asGetter(new TypeLiteral<Iterable<String>>() {
            });
            Provider<KeyedObjectFactory<String, Model>> factoryProvider = builder.getKeyedObjectFactoryProvider(getterExpression);
            return new EachTemplate(expression, key, content, factoryProvider);
        }

        private static class EachTemplate implements Writable {
            private final Expression<Void, Iterable<String>> expression;
            private final String key;
            private final Writable content;
            private final Provider<KeyedObjectFactory<String, Model>> factoryProvider;

            EachTemplate(Expression<Void, Iterable<String>> expression, String key, Writable content, Provider<KeyedObjectFactory<String, Model>> factoryProvider) {
                this.expression = expression;
                this.key = key;
                this.content = content;
                this.factoryProvider = factoryProvider;
            }

            public void write(Writer writer, WritableContext context) throws IOException {
                KeyedObjectFactory<String, Model> factory = factoryProvider.get();
                for (String arg : context.call(expression)) {
                    Model model = factory.create(arg);
                    WritableContext childContext = context.putModelWithUniqueArg(model, key, arg, expression);
                    content.write(writer, childContext);
                }
            }
        }
    }

    private static class MapHandler extends Handler {
        MapHandler() {
            super("map");
        }

        @Override
        public Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "map");
            String[] args = value.split(",");

            Expression<String, Model> keyedGetterExpression;
            if (args.length == 2) {
                keyedGetterExpression = builder.call(args[1]).asKeyedChild();
            } else if (args.length == 1) {
                keyedGetterExpression = builder.call(args[0] + ".get").asKeyedChild();
            } else {
                throw new RuntimeException("Bad parameter: wug:map=" + value);
            }
            ControllerBuilder childController = builder.getChildWithKey(keyedGetterExpression);

            Writable content = chain(element, output, childController);
            Expression<Void, Iterable<Map.Entry<String, Model>>> expression = builder.call(args[0]).asMap(keyedGetterExpression);
            return new MapTemplate(expression, content);
        }

        private static class MapTemplate implements Writable {
            private final Expression<Void, Iterable<Map.Entry<String, Model>>> expression;
            private final Writable child;

            MapTemplate(Expression<Void, Iterable<Map.Entry<String, Model>>> expression, Writable child) {
                this.expression = expression;
                this.child = child;
            }

            public void write(Writer writer, WritableContext context) throws IOException {
                for (Map.Entry<String, Model> entry : context.call(expression)) {
                    WritableContext writableContext = context.putModelWithArg(entry.getValue(), entry.getKey(), expression);
                    child.write(writer, writableContext);
                }
            }
        }
    }

    private static class RenderHandler extends Handler {
        RenderHandler() {
            super("render");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "render");
            String[] args = value.split(",");

            Expression<Renderer<String, Model>, Void> renderExpression = builder.call(args[0]).asRender();
            Expression<String, Model> getterExpression;
            if (args.length == 2) {
                getterExpression = builder.call(args[1]).asKeyedChild(renderExpression);
            } else if (args.length == 1) {
                getterExpression = new GetByRenderExpression(renderExpression);
            } else {
                throw new RuntimeException("Bad parameter: wug:render=" + value);
            }
            ControllerFactory.ControllerBuilder childBuilder = builder.getChildWithKey(getterExpression);

            Writable child = chain(element, output, childBuilder);
            return new RenderTemplate(renderExpression, child);
        }

        private static class RenderTemplate implements Writable {
            private final Expression<Renderer<String, Model>, Void> expression;
            private final Writable child;

            RenderTemplate(Expression<Renderer<String, Model>, Void> expression, Writable child) {
                this.expression = expression;
                this.child = child;
            }

            public void write(Writer writer, WritableContext context) throws IOException {
                RendererImpl renderer = new RendererImpl(writer, child, context, expression);
                context.call(expression, renderer);
                renderer.done();
            }
        }

        private static class RendererImpl implements Renderer<String, Model> {
            private final Writer writer;
            private final Writable template;
            private final WritableContext context;
            private final Expression<Renderer<String, Model>, Void> expression;
            private IOException exception;

            RendererImpl(Writer writer, Writable template, WritableContext context, Expression<Renderer<String, Model>, Void> expression) {
                this.writer = writer;
                this.template = template;
                this.context = context;
                this.expression = expression;
            }

            public void render(String key, Model model) {
                if (exception != null) {
                    return;
                }
                WritableContext newContext = context.putModelWithArg(model, key, expression);
                try {
                    template.write(writer, newContext);
                } catch (IOException e) {
                    exception = e;
                }
            }

            void done() throws IOException {
                if (exception != null) {
                    throw exception;
                }
            }
        }

        private static class GetByRenderExpression implements Expression<String, Model> {
            private final Expression<Renderer<String, Model>, Void> render;

            GetByRenderExpression(Expression<Renderer<String, Model>, Void> render) {
                this.render = render;
            }

            public List<String> asStrings() {
                return render.asStrings();
            }

            public ModelFactory getModelFactory() {
                return render.getModelFactory();
            }

            public Model call(Model model, String key) {
                GetterRenderer renderer = new GetterRenderer(key);
                render.call(model, renderer);
                return renderer.get();
            }

            public Model tryCall(Model model, String key) throws ModelException {
                GetterRenderer renderer = new GetterRenderer(key);
                render.tryCall(model, renderer);
                return renderer.get();
            }

            private static class GetterRenderer implements Renderer<String, Model> {
                private final String key;
                private Model value;

                GetterRenderer(String key) {
                    this.key = key;
                }

                public void render(String key, Model value) {
                    if (this.key.equals(key)) {
                        this.value = value;
                    }
                }

                Model get() {
                    return value;
                }
            }
        }
    }

    private static class AttrHandler extends Handler {
        AttrHandler() {
            super("attr");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "attr");
            for (Map.Entry<String, String> entry : TemplateEngine.splitAttrSet(value).entrySet()) {
                String attribute = entry.getKey();
                String name = entry.getValue();
                Expression<Void, String> expression = builder.call(name).asString();
                Writable template = new AttributeTemplate(attribute, expression);
                output.putAttribute(attribute, template);
            }

            return chain(element, output, builder);
        }
    }

    private static class ConstantAttributeTemplate implements Writable {
        private final String name;
        private final String value;

        ConstantAttributeTemplate(String name, String value) {
            this.name = name;
            this.value = escapeAttribute(value);
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            if (value != null) {
                writer.write(" ");
                writer.write(name);
                writer.write("=\"");
                writer.write(value);
                writer.write("\"");
            }
        }
    }

    private static class AttributeTemplate implements Writable {
        private final String attribute;
        private final Expression<Void, String> expression;

        AttributeTemplate(String attribute, Expression<Void, String> expression) {
            this.attribute = attribute;
            this.expression = expression;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            String value = context.getString(expression);
            if (value != null) {
                writer.write(" ");
                writer.write(attribute);
                writer.write("=\"");
                writer.write(escapeAttribute(value));
                writer.write("\"");
            }
        }
    }

    private static class ResourceAttrHandler extends Handler {
        ResourceAttrHandler() {
            super("resource-attr");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) {
            String value = element.getAttributeNS(TemplateEngine.NS, "resource-attr");
            for (Map.Entry<String, String> entry : TemplateEngine.splitAttrSet(value).entrySet()) {
                output.putAttribute(entry.getKey(), new ResourceAttrTemplate(entry.getKey(), entry.getValue()));
            }
            return chain(element, output, builder);
        }
    }

    static Map<String, String> splitAttrSet(String string) {
        Map<String, String> map = new HashMap<String, String>();
        for (String pair : string.split(",")) {
            int n = pair.indexOf(":");
            String key;
            String value;
            if (n == -1) {
                key = pair;
                value = pair;
            } else {
                key = pair.substring(0, n);
                value = pair.substring(n + 1);
            }
            map.put(key, value);
        }
        return map;
    }

    private static class ResourceAttrTemplate implements Writable {
        private final String attribute;
        private final String key;

        ResourceAttrTemplate(String attribute, String key) {
            this.attribute = attribute;
            this.key = key;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            String value = context.get(new TypeLiteral<Provider<ResourceBundle>>() {
            }).get().getString(key);
            if (value != null) {
                writer.write(" ");
                writer.write(attribute);
                writer.write("=\"");
                writer.write(escapeAttribute(value));
                writer.write("\"");
            }
        }
    }


    private static class Html {
        private static class ControlNameTemplate implements Writable {
            private final String name;
            private final int depth;

            ControlNameTemplate(String name, int depth) {
                this.name = name;
                this.depth = depth;
            }

            public void write(Writer writer, WritableContext context) throws IOException {
                writer.write(" name=\"");
                writer.write(context.getNameForFormControl(name, depth));
                writer.write("\"");
            }
        }

        private static class IdTemplate implements Writable {
            private String name;

            IdTemplate(String name) {
                this.name = name;
            }

            public void write(Writer writer, WritableContext context) throws IOException {
                writer.write(" id=\"");
                writer.write(context.getNameWithPathArgs(name));
                writer.write("\"");
            }
        }

        private static class ActionHandler extends Handler {
            ActionHandler() {
                super("action");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "action");
                Expression<Void, Void> expression = builder.call(value).asAction();
                String actionKey = builder.registerAction(expression);
                Writable template = new ActionTemplate(actionKey);
                output.putAttribute("href", template);
                return chain(element, output, builder);
            }

            private static class ActionTemplate implements Writable {
                private final String name;

                ActionTemplate(String name) {
                    this.name = name;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
                    writer.write(" href=\"");
                    String url = context.getActionURL(name);
                    writer.write(escapeAttribute(url));
                    writer.write("\"");
                }
            }
        }

        private static class LinkHandler extends Handler {
            LinkHandler() {
                super("link");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "link");
                Expression<Void, Model> expression = builder.call(value).asChild();
                ControllerBuilder childBuilder = builder.getChild(expression);
                Class type = childBuilder.getType().getRawType();
                Provider<PageFactory.Template> template = builder.getTemplateForPage(type);
                Writable writable = new LinkTemplate(template, expression);
                output.putAttribute("href", writable);
                return chain(element, output, builder);
            }

            private static class LinkTemplate implements Writable {
                Provider<PageFactory.Template> template;
                private final Expression<Void, Model> expression;

                private LinkTemplate(Provider<PageFactory.Template> template, Expression<Void, Model> expression) {
                    this.template = template;
                    this.expression = expression;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
                    writer.write(" href=\"");
                    String url = context.getLinkURL(template.get(), expression);
                    writer.write(escapeAttribute(url));
                    writer.write("\"");
                }
            }
        }

        private static class FormHandler extends Handler {
            FormHandler() {
                super("form");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "form");
                Expression<Void, Model> expression = builder.call(value).asChild();
                ControllerBuilder childController = builder.getChild(expression);
                String formKey = childController.registerForm();
                output.putAttribute("action", new FormTemplate());
                output.putAttribute("method", new StringTemplate(" method=\"post\""));
                output.insert(new PostTemplate(formKey));
                Writable child = chain(element, output, childController);
                return new ChildTemplate(expression, child);
            }

            private static class FormTemplate implements Writable {
                public void write(Writer writer, WritableContext context) throws IOException {
                    writer.write(" action=\"");
                    String url = context.getPostURL();
                    writer.write(escapeAttribute(url));
                    writer.write("\"");
                }
            }

            private static class PostTemplate implements Writable {
                private final String name;

                private PostTemplate(String name) {
                    this.name = name;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
                    writer.write("<input type=\"hidden\" name=\"_post\" value=\"");
                    String url = context.getPostParameter(name);
                    writer.write(escapeAttribute(url));
                    writer.write("\"/>");
                }
            }
        }

        private static class InputHandler extends Handler {
            InputHandler() {
                super("input");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "input");
                String label;
                if (element.hasAttribute("name")) {
                    label = element.getAttribute("name");
                } else {
                    label = value.replace(".", "_");
                }

                Expression<Void, String[]> getter = builder.call(value).asStringsGetter();
                Expression<String[], Void> setter = builder.call(value).asStringsSetter();
                String name = builder.registerControl(getter, setter, label);
                String catchKey = builder.registerCatch(value, TypeLiteral.get(Exception.class));
                output.putAttribute("value", new InputTemplate(getter, catchKey));
                output.putAttribute("name", new ControlNameTemplate(name, builder.getControlDepth()));
                output.putAttribute("id", new IdTemplate(builder.registerId(getter)));
                return chain(element, output, builder);
            }

            private static class InputTemplate implements Writable {
                private final Expression<Void, String[]> expression;
                private final String catchKey;

                InputTemplate(Expression<Void, String[]> expression, String catchKey) {
                    this.expression = expression;
                    this.catchKey = catchKey;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
                    writer.write(" value=\"");
                    writeValue(writer, context);
                    writer.write("\"");
                }

                private void writeValue(Writer writer, WritableContext context) throws IOException {
                    List<ModelException> modelExceptions = context.getExceptions(TypeLiteral.get(Exception.class), catchKey);
                    String alternativeValue = getAlternativeValue(modelExceptions);
                    if (alternativeValue != null) {
                        writer.write(alternativeValue);
                    } else {
                        String[] value = context.call(expression);
                        if (value.length > 0 && value[0] != null) {
                            writer.write(escapeAttribute(value[0]));
                        }
                    }
                }

                private String getAlternativeValue(List<ModelException> modelExceptions) {
                    for (ModelException modelException : modelExceptions) {
                        if (modelException.hasAlternativeValue()) {
                            return modelException.getAlternativeValue();
                        }
                    }
                    return null;
                }
            }
        }

        private static class SelectHandler extends Handler {
            SelectHandler() {
                super("select");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "select");
                String label;
                if (element.hasAttribute("name")) {
                    label = element.getAttribute("name");
                } else {
                    label = value.replace(".", "_");
                }

                Expression<Void, String[]> getter = builder.call(value).asStringsGetter();
                Expression<String[], Void> setter = builder.call(value).asStringsSetter();

                String name = builder.registerControl(getter, setter, label);
                Writable nameTemplate = new ControlNameTemplate(name, builder.getControlDepth());
                output.putAttribute("name", nameTemplate);
                output.putAttribute("id", new IdTemplate(builder.registerId(getter)));
                Writable child = chain(element, output, builder);
                return new SelectTemplate(getter, child);
            }

            private static class SelectTemplate implements Writable {
                private final Expression<Void, String[]> expression;
                private final Writable child;

                private SelectTemplate(Expression<Void, String[]> expression, Writable child) {
                    this.expression = expression;
                    this.child = child;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
                    String[] value = context.call(expression);
                    WritableContext newContext = context.push(TypeLiteral.get(SelectHandlerCurrentValue.class), new SelectHandlerCurrentValue(value));
                    child.write(writer, newContext);
                }
            }
        }

        private static class SelectHandlerCurrentValue {
            private final Set<String> value;

            private SelectHandlerCurrentValue(String[] strings) {
                this(new HashSet<String>(Arrays.asList(strings)));

            }

            private SelectHandlerCurrentValue(Set<String> value) {
                this.value = value;
            }

            Set<String> getValue() {
                return value;
            }
        }

        private static class OptionHandler extends Handler {
            OptionHandler() {
                super("option");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "option");

                Expression<Renderer<String, String>, Void> expression = builder.call(value).asOptionRender();
                ModelFactory optionEntryModelFactory = new SimpleModelFactory(TypeLiteral.get(OptionEntry.class));
                ControllerBuilder childControllerBuilder = builder.getChildWithKey(new DummyExpression(optionEntryModelFactory));

                Expression<Void, String> keyExpression = childControllerBuilder.call("key").asString();
                Expression<Void, String> valueExpression = childControllerBuilder.call("value").asString();

                output.putAttribute("selected", new OptionSelectedTemplate(keyExpression));
                output.putAttribute("value", new AttributeTemplate("value", keyExpression));
                output.setContent(new LabelTemplate(valueExpression));

                Writable child = chain(element, output, childControllerBuilder);
                return new OptionTemplate(child, optionEntryModelFactory, expression);
            }

            private static class DummyExpression implements Expression<String, Model> {
                private final ModelFactory modelFactory;

                private DummyExpression(ModelFactory modelFactory) {
                    this.modelFactory = modelFactory;
                }

                public Model call(Model model, String s) {
                    throw new UnsupportedOperationException("can't call action under wug:option");
                }

                public Model tryCall(Model model, String s) throws ModelException {
                    throw new UnsupportedOperationException("can't call action under wug:option");
                }

                public ModelFactory getModelFactory() {
                    return modelFactory;
                }

                public List<String> asStrings() {
                    return Collections.emptyList();
                }
            }

            private static class OptionSelectedTemplate implements Writable {
                private final Expression<Void, String> expression;

                private OptionSelectedTemplate(Expression<Void, String> expression) {
                    this.expression = expression;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
                    SelectHandlerCurrentValue currentValue = context.get(TypeLiteral.get(SelectHandlerCurrentValue.class));
                    String key = context.call(expression);
                    if (currentValue == null ? (key == null) : currentValue.getValue().contains(key)) {
                        writer.write(" selected=\"selected\"");
                    }
                }
            }

            public static class OptionEntry<K, V> implements Map.Entry<K, V> {
                private final K key;
                private V value;

                OptionEntry(K key, V value) {
                    this.key = key;
                    this.value = value;
                }

                public K getKey() {
                    return key;
                }

                public V getValue() {
                    return value;
                }

                public V setValue(V value) {
                    V oldValue = this.value;
                    this.value = value;
                    return oldValue;
                }
            }

            private static class OptionTemplate implements Writable {
                private final Expression<Renderer<String, String>, Void> expression;
                private final ModelFactory modelFactory;
                private final Writable child;

                OptionTemplate(Writable child, ModelFactory modelFactory, Expression<Renderer<String, String>, Void> expression) {
                    this.child = child;
                    this.modelFactory = modelFactory;
                    this.expression = expression;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
                    RendererImpl<String, String> renderer = new RendererImpl<String, String>(writer, context, child, modelFactory, expression);
                    context.call(expression, renderer);
                    renderer.done();
                }


                private static class RendererImpl<K, V> implements Renderer<K, V> {
                    private final WritableContext context;
                    private final Writer writer;
                    private final Writable child;
                    private final ModelFactory modelFactory;
                    private final Expression<Renderer<String, String>, Void> renderExpression;
                    private IOException exception;

                    RendererImpl(Writer writer, WritableContext context, Writable child, ModelFactory modelFactory, Expression<Renderer<String, String>, Void> renderExpression) {
                        this.writer = writer;
                        this.context = context;
                        this.child = child;
                        this.modelFactory = modelFactory;
                        this.renderExpression = renderExpression;
                    }

                    public void render(Object key, Object value) {
                        if (exception == null) {
                            String v = value == null ? null : value.toString();
                            String k = key == null ? null : key.toString();
                            Model model = modelFactory.create(new OptionEntry<String, String>(k, v));
                            WritableContext childContext = context.putModelWithArg(model, k, renderExpression);
                            try {
                                child.write(writer, childContext);
                            } catch (IOException e) {
                                exception = e;
                            }
                        }
                    }

                    void done() throws IOException {
                        if (exception != null) {
                            throw exception;
                        }
                    }
                }
            }
        }

        private static class RadioHandler extends Handler {
            RadioHandler() {
                super("radio");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "radio");
                String label;
                if (element.hasAttribute("name")) {
                    label = element.getAttribute("name");
                } else {
                    label = value.replace(".", "_");
                }

//                modelFactory.bindStrings(value);
                Expression<Void, String[]> getter = builder.call(value).asStringsGetter();
                Expression<String[], Void> setter = builder.call(value).asStringsSetter();

                String name = builder.registerControl(getter, setter, label);

//                modelFactory.bindStrings(value);
//                builder.createCall(value).asControl(element.getAttribute("name"));
                Writable checkedTemplate = new RadioCheckedTemplate(value, element.getAttribute("value"));
                output.putAttribute("checked", checkedTemplate);
                Writable nameTemplate = new ControlNameTemplate(name, builder.getControlDepth());
                output.putAttribute("name", nameTemplate);
                output.putAttribute("id", new IdTemplate(builder.registerId(getter)));
                return chain(element, output, builder);
            }

            private static class RadioCheckedTemplate implements Writable {
                private final String name;
                private final String value;

                RadioCheckedTemplate(String name, String value) {
                    this.name = name;
                    this.value = value;
                }

                public void write(Writer writer, WritableContext context) throws IOException {
//                    if (value.equals(context.getModel().getString(name))) {
//                        writer.write(" checked=\"checked\"");
//                    }
                    throw new AssertionError("FIXME: radio not implemented");
                }
            }
        }

        private static class SubmitHandler extends Handler {
            SubmitHandler() {
                super("submit");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "submit");
                String name = element.getAttribute("name");
                if (name.length() == 0) {
                    name = value.replace(".", "_");
                }
                Expression<Void, Void> expression = builder.call(value).asSubmit();
                String postKey = builder.registerSubmit(expression, name);
                Writable nameTemplate = new ControlNameTemplate(postKey, builder.getControlDepth());
                output.putAttribute("name", nameTemplate);

                return chain(element, output, builder);
            }
        }

        private static class ForHandler extends Handler {
            ForHandler() {
                super("for");
            }

            @Override
            Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
                String value = element.getAttributeNS(TemplateEngine.NS, "for");
                Expression<Void, String[]> expression = builder.call(value).asStringsGetter();
                output.putAttribute("for", new ForTemplate(builder.registerId(expression)));
                return chain(element, output, builder);
            }
        }

        private static class ForTemplate implements Writable {
            private final String name;

            ForTemplate(String name) {
                this.name = name;
            }

            public void write(Writer writer, WritableContext context) throws IOException {
                writer.write(" for=\"");
                writer.write(context.getNameWithPathArgs(name));
                writer.write("\"");
            }
        }
    }

    private static class LabelHandler extends Handler {
        LabelHandler() {
            super("label");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "label");
            Expression<Void, String> expression = builder.call(value).asString();
            output.setContent(new LabelTemplate(expression));
            return chain(element, output, builder);
        }
    }

    private static class LabelTemplate implements Writable {
        private final Expression<Void, String> expression;

        LabelTemplate(Expression<Void, String> expression) {
            this.expression = expression;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            String result = context.getString(expression);
            if (result != null) {
                writer.write(escapeText(result));
            }
        }

        private String escapeText(String string) {
            return string.replace("&", "&amp;").replace("<", "&lt;").replace("<", "&gt;");
        }
    }

    private static class ResourceHandler extends Handler {
        ResourceHandler() {
            super("resource");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "resource");
            output.setContent(new ResourceTemplate(value));
            return chain(element, output, builder);
        }
    }

    private static class ResourceTemplate implements Writable {
        private final String key;

        private ResourceTemplate(String key) {
            this.key = key;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            Provider<ResourceBundle> resourceBundleProvider = context.get(new TypeLiteral<Provider<ResourceBundle>>() {
            });
            String value = resourceBundleProvider.get().getString(key);
            writer.write(TemplateEngine.escapeText(value));
        }
    }


    private static class ExportHandler extends Handler {
        ExportHandler() {
            super("export");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            try {
                builder.registerExport(element);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(element + ": ", e);
            }
            return new StringTemplate("");
        }
    }

    private static class ImportHandler extends Handler {
        ImportHandler() {
            super("import");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "import");
            String[] args = value.split(",");

            Expression<Void, Model> expression = builder.call(args[0]).asChild();
            String key = builder.registerImport(expression);
            Class<?> type = builder.getTypeForImport(key);
            Provider<PageFactory.Template> template = builder.getTemplateForImport(type);
            Writable imported = new ImportWritable(template, key);
            Writable content = new ChildTemplate(expression, imported);
            output.setContent(content);

            return chain(element, output, builder);
        }

        private static class ImportWritable implements Writable {
            private final Provider<PageFactory.Template> templateProvider;
            private final String key;

            private ImportWritable(Provider<PageFactory.Template> templateProvider, String key) {
                this.templateProvider = templateProvider;
                this.key = key;
            }

            public void write(Writer writer, WritableContext context) throws IOException {
                context.writeExported(writer, key, templateProvider.get());
            }
        }
    }


    private static class RawHandler extends Handler {
        RawHandler() {
            super("raw");
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) throws NoSuchMethodException {
            String value = element.getAttributeNS(TemplateEngine.NS, "raw");
            Expression<Void, String> expression = builder.call(value).asString();
            output.setContent(new RawTemplate(expression));
            return chain(element, output, builder);
        }
    }

    private static class RawTemplate implements Writable {
        private final Expression<Void, String> expression;

        RawTemplate(Expression<Void, String> expression) {
            this.expression = expression;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            String result = context.call(expression);
            if (result != null) {
                writer.write(result);
            }
        }
    }

    private static class LastHandler extends Handler {
        private final TemplateEngine templateEngine;

        LastHandler(TemplateEngine templateEngine) {
            this.templateEngine = templateEngine;
        }

        @Override
        Writable construct(Element element, Output output, ControllerFactory.ControllerBuilder builder) {
            return output.construct(templateEngine, element, builder);
        }
    }

    private static class ElementTemplate implements Writable {
        private final String name;
        private final Writable[] attributes;
        private final Writable[] writables;

        ElementTemplate(String prefix, String localName, Writable[] attributes, Writable[] writables) {
            this.name = (prefix == null ? "" : prefix + ":") + localName;
            this.attributes = attributes.clone();
            this.writables = writables;
        }

        public void write(Writer writer, WritableContext context) throws IOException {
            writer.write("<");
            writer.write(name);
            for (Writable attribute : attributes) {
                attribute.write(writer, context);
            }
            if (writables.length == 0) {
                writer.write("/>");
            } else {
                writer.write(">");
                for (Writable writable : writables) {
                    writable.write(writer, context);
                }
                writer.write("</");
                writer.write(name);
                writer.write(">");
            }
        }
    }
}